﻿/**

 * Copyright (c) 2015-2016, FastDev 刘强 (fastdev@163.com).

 *

 * Licensed under the Apache License, Version 2.0 (the "License");

 * you may not use this file except in compliance with the License.

 * You may obtain a copy of the License at

 *

 *      http://www.apache.org/licenses/LICENSE-2.0

 *

 * Unless required by applicable law or agreed to in writing, software

 * distributed under the License is distributed on an "AS IS" BASIS,

 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

 * See the License for the specific language governing permissions and

 * limitations under the License.

 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Threading.Tasks;
using OF.DistributeService.Core.Client.Entity;
using OF.DistributeService.Core.Entity;
using OF.DistributeService.Core.Common;
using System.Collections.Concurrent;

namespace OF.DistributeService.Core.Client
{
    public class EmitHelper
    {
        public static Type[] emptyType = new Type[0];
        public static Type objectType = typeof(object);
        public static Type iCommonApiCallProxyType = typeof(ICommonApiCallProxy);
        public static ConstructorInfo objCtor = objectType.GetConstructor(new Type[0]);
        public static Type CommonApiCallContextType = typeof(CommonApiCallContext);
        public static Type CommonApiCallContextParamType = typeof(CommonApiCallContextParam);
        public static FieldInfo CommonApiCallContextAPIProxyField = CommonApiCallContextType.GetField("APIProxy");
        private static MethodInfo GetTypeFromHandleMethod = typeof(Type).GetMethod("GetTypeFromHandle", new Type[] { typeof(RuntimeTypeHandle) });
        public static FieldInfo CommonApiCallContextReturnTypeField = CommonApiCallContextType.GetField("ReturnType");
        public static FieldInfo CommonApiCallContextMethodNameField = CommonApiCallContextType.GetField("MethodName");
        public static FieldInfo CommonApiCallContextParamsField = CommonApiCallContextType.GetField("Params");
        public static FieldInfo CommonApiCallContextParamParamNameField = CommonApiCallContextParamType.GetField("ParamName");
        public static FieldInfo CommonApiCallContextParamParamTypeField = CommonApiCallContextParamType.GetField("ParamType");
        public static FieldInfo CommonApiCallContextParamParamValueField = CommonApiCallContextParamType.GetField("ParamValue");
        public static MethodInfo iCommonApiCallProxyInvokeMethod = iCommonApiCallProxyType.GetMethod("Invoke");
        public static MethodInfo iCommonApiCallProxyInvokeVoidMethod = iCommonApiCallProxyType.GetMethod("InvokeVoid");
        private static Type CommonApiCallContextParamListType = typeof(List<CommonApiCallContextParam>);
        public static MethodInfo CommonApiCallContextParamListAddMethod = CommonApiCallContextParamListType.GetMethod("Add", new Type[] { CommonApiCallContextParamType });
        public static Type VoidType = Type.GetType("System.Void");
        public static Type ClientToAppServiceMetaType = typeof(ClientToAppServiceMeta);

        public static int ClassIndex = 0;
        private AssemblyBuilder assemblyBuilder = null;
        private ModuleBuilder moduleBuilder = null;
        private static ConcurrentDictionary<Type, Type> interfaceImpDict = new ConcurrentDictionary<Type, Type>();
        public EmitHelper()
        {
            string fullName = "OF.DistributeService.Core.Common.EmitGenerated.EmitHelperContainer";
            assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName(fullName), AssemblyBuilderAccess.Run);//AssemblyBuilderAccess.RunAndSave   //assemblyBuilder.Save(fullName + ".dll");
            moduleBuilder = assemblyBuilder.DefineDynamicModule(fullName);//, fullName + ".dll"
        }

        public object GetAPIProxy(ClientToAppServiceMeta clientToAppServiceMeta, ICommonApiCallProxy iCommonApiCallProxy, Type interfaceType)
        {
            Type impType = null;
            if (!interfaceImpDict.TryGetValue(interfaceType, out impType))
            {
                string typeName = interfaceType.Name;
                int classIndex = System.Threading.Interlocked.Increment(ref ClassIndex);
                string fullName = "OF.DistributeService.Core.Common.EmitGenerated." + typeName + "_" + classIndex;
                TypeBuilder typeBuilder = moduleBuilder.DefineType(fullName, TypeAttributes.Public | TypeAttributes.BeforeFieldInit, objectType, new Type[] { interfaceType });
                FieldBuilder commonCallProxy = typeBuilder.DefineField("commonCallProxy", iCommonApiCallProxyType, FieldAttributes.Private);
                FieldBuilder serviceMeta = typeBuilder.DefineField("serviceMeta", ClientToAppServiceMetaType, FieldAttributes.Private);
                ConstructorBuilder ivCtor = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new Type[] { iCommonApiCallProxyType, ClientToAppServiceMetaType });
                ILGenerator ctorIL = ivCtor.GetILGenerator();
                ctorIL.Emit(OpCodes.Ldarg_0);
                ctorIL.Emit(OpCodes.Call, objCtor);
                ctorIL.Emit(OpCodes.Ldarg_0);
                ctorIL.Emit(OpCodes.Ldarg_1);
                ctorIL.Emit(OpCodes.Stfld, commonCallProxy);
                ctorIL.Emit(OpCodes.Ldarg_0);
                ctorIL.Emit(OpCodes.Ldarg_2);
                ctorIL.Emit(OpCodes.Stfld, serviceMeta);
                ctorIL.Emit(OpCodes.Ret);
                foreach (var methodInfo in interfaceType.GetMethods())
                {
                    ParameterInfo[] parameterInfoArray = methodInfo.GetParameters();
                    Type returnType = methodInfo.ReturnType;
                    List<Type> paramTypeList = parameterInfoArray.Select(item => item.ParameterType).ToList();
                    MethodBuilder methodBuilder = typeBuilder.DefineMethod(methodInfo.Name, MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual, returnType, paramTypeList.ToArray());
                    ILGenerator methodIL = methodBuilder.GetILGenerator();
                    LocalBuilder context = methodIL.DeclareLocal(CommonApiCallContextType);
                    LocalBuilder contextParam = methodIL.DeclareLocal(CommonApiCallContextParamType);
                    methodIL.Emit(OpCodes.Newobj, CommonApiCallContextType.GetConstructor(emptyType));
                    methodIL.Emit(OpCodes.Stloc, context);
                    methodIL.Emit(OpCodes.Ldloc, context);
                    methodIL.Emit(OpCodes.Ldarg_0);
                    methodIL.Emit(OpCodes.Stfld, CommonApiCallContextAPIProxyField);
                    methodIL.Emit(OpCodes.Ldloc, context);
                    methodIL.Emit(OpCodes.Ldtoken, returnType);
                    methodIL.Emit(OpCodes.Call, GetTypeFromHandleMethod);
                    methodIL.Emit(OpCodes.Stfld, CommonApiCallContextReturnTypeField);

                    methodIL.Emit(OpCodes.Ldloc, context);
                    methodIL.Emit(OpCodes.Ldstr, methodInfo.Name);
                    methodIL.Emit(OpCodes.Stfld, CommonApiCallContextMethodNameField);

                    methodIL.Emit(OpCodes.Ldloc, context);
                    methodIL.Emit(OpCodes.Newobj, CommonApiCallContextParamListType.GetConstructor(emptyType));
                    methodIL.Emit(OpCodes.Stfld, CommonApiCallContextParamsField);
                    for (int paramI = 0; paramI < parameterInfoArray.Length; paramI++)
                    {
                        var paramInfo = parameterInfoArray[paramI];
                        methodIL.Emit(OpCodes.Ldloc, context);
                        methodIL.Emit(OpCodes.Ldfld, CommonApiCallContextParamsField);
                        methodIL.Emit(OpCodes.Newobj, CommonApiCallContextParamType.GetConstructor(emptyType));
                        methodIL.Emit(OpCodes.Stloc, contextParam);
                        methodIL.Emit(OpCodes.Ldloc, contextParam);
                        methodIL.Emit(OpCodes.Ldstr, paramInfo.Name);
                        methodIL.Emit(OpCodes.Stfld, CommonApiCallContextParamParamNameField);
                        methodIL.Emit(OpCodes.Ldloc, contextParam);
                        methodIL.Emit(OpCodes.Ldtoken, paramInfo.ParameterType);
                        methodIL.Emit(OpCodes.Call, GetTypeFromHandleMethod);
                        methodIL.Emit(OpCodes.Stfld, CommonApiCallContextParamParamTypeField);
                        methodIL.Emit(OpCodes.Ldloc, contextParam);
                        if (paramI == 0)
                        {
                            methodIL.Emit(OpCodes.Ldarg_1);
                        }
                        else if (paramI == 1)
                        {
                            methodIL.Emit(OpCodes.Ldarg_2);
                        }
                        else if (paramI == 2)
                        {
                            methodIL.Emit(OpCodes.Ldarg_3);
                        }
                        else
                        {
                            methodIL.Emit(OpCodes.Ldarg_S, paramI + 1);
                        }
                        if (paramInfo.ParameterType.IsValueType)
                        {
                            methodIL.Emit(OpCodes.Box, paramInfo.ParameterType);
                        }
                        methodIL.Emit(OpCodes.Stfld, CommonApiCallContextParamParamValueField);
                        methodIL.Emit(OpCodes.Ldloc, contextParam);
                        methodIL.Emit(OpCodes.Callvirt, CommonApiCallContextParamListAddMethod);
                    }
                    methodIL.Emit(OpCodes.Ldarg_0);
                    methodIL.Emit(OpCodes.Ldfld, commonCallProxy);
                    methodIL.Emit(OpCodes.Ldloc, context);
                    methodIL.Emit(OpCodes.Ldarg_0);
                    methodIL.Emit(OpCodes.Ldfld, serviceMeta);
                    MethodInfo genInvokeMethod = null;
                    if (returnType != VoidType)
                    {
                        genInvokeMethod = iCommonApiCallProxyInvokeMethod.MakeGenericMethod(new Type[] { returnType });
                    }
                    else
                    {
                        genInvokeMethod = iCommonApiCallProxyInvokeVoidMethod;
                    }
                    methodIL.Emit(OpCodes.Callvirt, genInvokeMethod);
                    methodIL.Emit(OpCodes.Ret);
                }
                impType = typeBuilder.CreateType();
                interfaceImpDict.AddOrUpdate(interfaceType, impType, (k, v) => impType);
            }
            object newObj = impType.GetConstructor(new Type[] { iCommonApiCallProxyType, ClientToAppServiceMetaType }).Invoke(new object[] { iCommonApiCallProxy, clientToAppServiceMeta });
            return newObj;
        }

        public T GetAPIProxy<T>(ClientToAppServiceMeta clientToAppServiceMeta, ICommonApiCallProxy iCommonApiCallProxy) where T : class
        {
            Type interfaceType = typeof(T);
            object newObj = GetAPIProxy(clientToAppServiceMeta, iCommonApiCallProxy, interfaceType);
            return newObj as T;
        }
    }
}